package uk.ac.manchester.tewpl.iceiface.client.lists;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import uk.ac.manchester.tewpl.iceiface.client.Functors.IterativeLoader;
import uk.ac.manchester.tewpl.iceiface.client.Functors.ParamVoidCallback;
import uk.ac.manchester.tewpl.iceiface.client.Functors.VoidCallback;
import uk.ac.manchester.tewpl.iceiface.client.model.KBClassModel;
import uk.ac.manchester.tewpl.iceiface.client.model.KBIndividualModel;
import uk.ac.manchester.tewpl.iceiface.client.model.KBObjectPropertyModel;
import uk.ac.manchester.tewpl.iceiface.client.model.KBResourceModel;
import uk.ac.manchester.tewpl.iceiface.client.model.KBWorkspaceModel;
import uk.ac.manchester.tewpl.iceiface.client.service.KBServiceAsync;

import com.extjs.gxt.ui.client.Registry;
import com.extjs.gxt.ui.client.Style.LayoutRegion;
import com.extjs.gxt.ui.client.Style.Orientation;
import com.extjs.gxt.ui.client.core.XTemplate;
import com.extjs.gxt.ui.client.data.BaseModelData;
import com.extjs.gxt.ui.client.event.ButtonEvent;
import com.extjs.gxt.ui.client.event.SelectionChangedEvent;
import com.extjs.gxt.ui.client.event.SelectionChangedListener;
import com.extjs.gxt.ui.client.event.SelectionListener;
import com.extjs.gxt.ui.client.store.GroupingStore;
import com.extjs.gxt.ui.client.util.Margins;
import com.extjs.gxt.ui.client.widget.Composite;
import com.extjs.gxt.ui.client.widget.ContentPanel;
import com.extjs.gxt.ui.client.widget.Html;
import com.extjs.gxt.ui.client.widget.Info;
import com.extjs.gxt.ui.client.widget.LayoutContainer;
import com.extjs.gxt.ui.client.widget.ListView;
import com.extjs.gxt.ui.client.widget.MessageBox;
import com.extjs.gxt.ui.client.widget.Text;
import com.extjs.gxt.ui.client.widget.button.Button;
import com.extjs.gxt.ui.client.widget.grid.ColumnConfig;
import com.extjs.gxt.ui.client.widget.grid.ColumnModel;
import com.extjs.gxt.ui.client.widget.grid.Grid;
import com.extjs.gxt.ui.client.widget.grid.GridGroupRenderer;
import com.extjs.gxt.ui.client.widget.grid.GridView;
import com.extjs.gxt.ui.client.widget.grid.GroupColumnData;
import com.extjs.gxt.ui.client.widget.grid.GroupingView;
import com.extjs.gxt.ui.client.widget.grid.RowExpander;
import com.extjs.gxt.ui.client.widget.layout.BorderLayout;
import com.extjs.gxt.ui.client.widget.layout.BorderLayoutData;
import com.extjs.gxt.ui.client.widget.layout.FitLayout;
import com.extjs.gxt.ui.client.widget.layout.RowData;
import com.extjs.gxt.ui.client.widget.layout.RowLayout;
import com.extjs.gxt.ui.client.widget.layout.VBoxLayout;
import com.extjs.gxt.ui.client.widget.toolbar.FillToolItem;
import com.extjs.gxt.ui.client.widget.toolbar.LabelToolItem;
import com.extjs.gxt.ui.client.widget.toolbar.ToolBar;
import com.google.gwt.user.client.rpc.AsyncCallback;

public class Lists extends Composite {
	
	// Widget containers
	private LayoutContainer container;
	private LayoutContainer listsContainer;
	private ContentPanel resultsPanel;
	
	// KnowledgeBase variables
	private KBServiceAsync kbservice = Registry.get("kbservice");
	private KBWorkspaceModel ws = Registry.get("workspace");
		
	// Initialisation variables
	boolean initStarted, initialised;
	
	// Tags data
	public static final String [] uris = new String [] {
			"http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#Positioning_Tag"};
	
	// TagList collection
	List<TagList> positionTagLists;
	
	// Constructor
	public Lists() {
		// Initialise top container
		container = new LayoutContainer();
		container.setLayout(new BorderLayout());
		
		// Return initialised container
		this.initComponent(container);
	}
	
	/**
	 * Initialise the widget, i.e. populate with data from KB
	 */
	public void init() {
		// Ensure init() is only called once
		if (!initStarted) {
			initStarted = true;
			container.mask("Reading position tags...");
			
			// Load position tags
			loadPositionTags();
		}
	}
	
	/*
	 * Called once execution tree started at loadPositionTags() has finished
	 */
	private void finishedInit() {		
		// Initialise lists container
		listsContainer = new LayoutContainer();
		listsContainer.setLayout(new RowLayout(Orientation.HORIZONTAL));
		
		BorderLayoutData listsContainerData = new BorderLayoutData(LayoutRegion.NORTH, 220);
		listsContainerData.setSplit(true);
		listsContainerData.setMargins(new Margins(4,0,0,4));
		
		// Initialise results panel
		resultsPanel = new ContentPanel();
		resultsPanel.setHeaderVisible(false);
		resultsPanel.setHeading("Results");
		
		BorderLayoutData resultsPanelData = new BorderLayoutData(LayoutRegion.CENTER);
		resultsPanelData.setSplit(true);
		resultsPanelData.setMargins(new Margins(4));
		
		// Add containers to top level container
		container.add(listsContainer, listsContainerData);
		container.add(resultsPanel, resultsPanelData);
		
		container.unmask();
		
		for (TagList tagList : positionTagLists) {
			listsContainer.add(tagList, new RowData((1.0 / positionTagLists.size()), 1, new Margins(0,4,0,0)));
		}
		
		// Create toolbar
		ToolBar toolBar = new ToolBar();
		final LabelToolItem countLabel = new LabelToolItem();
		
		// Set store model
		final GroupingStore<KBIndividualModel> dataStore = new GroupingStore<KBIndividualModel>();
		dataStore.groupBy("classes");
		
		Button btn = new Button("Run Query");
		btn.addSelectionListener(new SelectionListener<ButtonEvent>() {
			@Override
			public void componentSelected(ButtonEvent ce) {
				dataStore.removeAll();
				
				Collection<KBIndividualModel> checked = new ArrayList<KBIndividualModel>();

				for (TagList tagList : positionTagLists)
					for (KBIndividualModel indiv : tagList.getCheckedTags())
						checked.add(indiv);			
				
				if (checked.size() > 0) {
					// Build query
					StringBuffer sb = new StringBuffer();
					sb.append("PREFIX rdf:<http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
						+ "PREFIX rdfs:<http://www.w3.org/2000/01/rdf-schema#>\n"
						+ "PREFIX dc:<http://purl.org/dc/elements/1.1/>\n"
						+ "PREFIX CRAMPONv6:<http://medicine.manchester.ac.uk/TEWPL/CRAMPONv6#>\n"
						+ "SELECT ?x\n"
						+ "WHERE {\n");
					
					for (KBIndividualModel tag : checked) {
						sb.append("?x CRAMPONv6:isPositionedBy <" + tag.getUri() + ">.\n");
					}
					
					sb.append("}");
					
					String query = sb.toString();
					
					container.mask("Running query...");
					
					kbservice.executeQuery(query, ws, new AsyncCallback<Collection<Map<String,KBIndividualModel>>>() {
						@Override
						public void onFailure(Throwable arg0) {
							container.unmask();
							MessageBox.alert("Error", "Error when running query", null);
						}
						@Override
						public void onSuccess(Collection<Map<String, KBIndividualModel>> res) {
							for (Map<String, KBIndividualModel> map : res) {
								for (String key : map.keySet()) {
									dataStore.add(map.get(key));
								}
							}
							countLabel.setLabel("Result Count: " + res.size());
							container.unmask();
						}
					});
					
					
				} else {
					MessageBox.info("Make a selection", "Please select tags to build query!", null);
					countLabel.setLabel("");
				}
				
			}
		});
		
		toolBar.add(btn);
		toolBar.add(new FillToolItem());
		toolBar.add(countLabel);
		resultsPanel.setTopComponent(toolBar);

		// Set up columns
		ColumnConfig column = new ColumnConfig("label", "Label", 200);
		ColumnConfig column2 = new ColumnConfig("classes", "Classes", 100);
		column2.setHidden(true);
		
		// Create column collection
		List<ColumnConfig> configs = new ArrayList<ColumnConfig>();
		configs.add(column);
		configs.add(column2);
		
		// Set column model
		final ColumnModel cm = new ColumnModel(configs);
		
		GroupingView view = new GroupingView();
		view.setAutoFill(true);		
		view.setGroupRenderer(new GridGroupRenderer() {
			@Override
			public String render(GroupColumnData data) {
				String cls;
				try {
					cls = ((KBIndividualModel) data.models.iterator().next()).getClasses().iterator().next().getLabel();
				} catch (Exception e) {
					cls = cm.getColumnById(data.field).getHeader();
				}
				return cls + " (" + data.models.size() + ")";
			}
		});
		
		Grid<KBIndividualModel> grid = new Grid<KBIndividualModel>(dataStore, cm);
		grid.setHideHeaders(true);
		grid.setView(view);
		resultsPanel.setLayout(new FitLayout());
		resultsPanel.add(grid);
		
		// Update interface
		container.layout();
	}
	
	/*
	 * Load the position tag classes from the KB
	 */
	private void loadPositionTags() {
		kbservice.findClass(uris[0], ws, new AsyncCallback<KBClassModel>() {
			@Override
			public void onFailure(Throwable arg0) {
				MessageBox.alert("Error", "Failed to read position tags from ontology", null);
				container.el().mask("Failed to read position tags from ontology");
			}
			
			@Override
			public void onSuccess(KBClassModel cls) {
				
				kbservice.findSubClasses(cls, ws, new AsyncCallback<Collection<KBClassModel>>(){
					@Override
					public void onFailure(Throwable arg0) {				
						MessageBox.alert("Error", "Failed to read position tags from ontology", null);
						container.el().mask("Failed to read position tags from ontology");
					}

					@Override
					public void onSuccess(Collection<KBClassModel> classes) {
						// Create collection of TagLists
						loadTagLists(classes);
					}
					
				});
				
			}
			
		});
	}
	
	/*
	 * Create Tag Lists from given tag classes
	 */
	private void loadTagLists(Collection<KBClassModel> classes) {	
		// Initialise collection
		positionTagLists = new ArrayList<TagList>();
		
		// Define class model iterator
		final Iterator<KBClassModel> iter = classes.iterator();
		
		// _Define_ iterative loader
		IterativeLoader<KBClassModel, VoidCallback> iterLoader =
			new IterativeLoader<KBClassModel, VoidCallback>() {
				@Override
				public void load(final KBClassModel cls, final VoidCallback callback) {
					if (cls.getMemberCount() > 0) {
						container.el().mask("Loading tags for '" + cls.getLabel() + "'...");
						// Create a new taglist for this tag class
						TagList.create(cls, new ParamVoidCallback<TagList>() {
							@Override
							public void work(TagList tagList) {
								// Check if creation was successful
								if (tagList != null) // Add created TagList to collection
									positionTagLists.add(tagList);
								else // Inform user
									MessageBox.alert("Error", "Failed to load tag data for " + cls.getLabel(), null);
								
								// Continue
								if (iter.hasNext())
									load(iter.next(), callback);
								else
									callback.work();
							}
						});
					} else {
						// Continue
						if (iter.hasNext())
							load(iter.next(), callback);
						else
							callback.work();
					}
				}
		};
		
		// _Call_ iterative loader
		iterLoader.load(iter.next(), new VoidCallback() {
			@Override
			public void work() { // When all TagLists are created
				// Sort tag list collection			
				Collections.sort(positionTagLists, new Comparator<TagList>() {
					@Override
					public int compare(TagList list1, TagList list2) {
						return list1.getLabel().compareTo(list2.getLabel());
					}
				});
				finishedInit();
			}
		});
	}
	
}
